package Controller;

import ap.PositionTuple;
//import common.Req;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

public class FatTreeController {
    public static final String path = "D:\\temp\\fb\\";
    public static final String corePrefix = "ssw";
    public static final String aggregationPrefix = "fsw";
    public static final String edgePrefix = "rsw";
    public static final String hostPrefix = "h";

    Map<String, Set<PositionTuple>> coreToAggregation;
    Map<String, Set<PositionTuple>> aggregationToEdge;
    Map<String, Set<PositionTuple>> aggregationToCore;
    Map<String, Set<PositionTuple>> edgeToAggregation;
    Map<Integer, Set<String>> podToAggregation;
    Map<Integer, Set<String>> podToEdge;
    Map<String, Integer> switchToPod;
    Map<PositionTuple, PositionTuple> topology;
    Map<String, IpMatch> hostToIpMatch;
    Map<String, PositionTuple> edgeToHost;
    Map<String, PositionTuple> hostToEdge;
    Map<String, String> ipTable;
    Map<PositionTuple, Integer> socketPort;

    public static void main(String[] args) {
        FatTreeController ftc = new FatTreeController();
        ftc.readTopologyFile(path + "fbtopo.txt");
        ftc.readRuleAndSetHostIp();
        ftc.genDeviceConfig(path + "config/");
    }

    void genDeviceConfig(String path){

        genDeviceIP();
        genVirtualHost(path);
        genEdgeConfig(path);
        genAggregationConfig(path);
        genCoreConfig(path);
    }
    /*
        generate device ip in local for socket
        virtual host ip: 127.0.pod.(index+1)
        edge ip: 127.1.pod.(index+1)
        aggregation ip: 127.2.pod.(index+1)
        core ip: 127.3.index1.index2
     */
    void genDeviceIP(){
        for(String host: hostToEdge.keySet()){
            int portNum = 10000;
            socketPort.put(hostToEdge.get(host), portNum);

            String[] token = host.split("-");
            int podIndex = Integer.parseInt(token[1]);
            int index = Integer.parseInt(token[2]);
            String ip = String.format("127.0.%d.%d", podIndex, index+1);
            ipTable.put(host, ip);
        }

        for(String edge: edgeToHost.keySet()){
            int portNum = 10000;
            for(PositionTuple pt: edgeToAggregation.get(edge)){
                socketPort.put(pt, portNum++);
            }

            String[] token = edge.split("-");
            int podIndex = Integer.parseInt(token[1]);
            int index = Integer.parseInt(token[2]);
            String ip = String.format("127.1.%d.%d", podIndex, index+1);
            ipTable.put(edge, ip);
            socketPort.put(edgeToHost.get(edge), portNum++);
        }

        for(String aggregation: aggregationToEdge.keySet()){
            int portNum = 10000;
            for(PositionTuple pt: aggregationToEdge.get(aggregation)){
                socketPort.put(pt, portNum++);
            }
            for(PositionTuple pt: aggregationToCore.get(aggregation)){
                socketPort.put(pt, portNum++);
            }

            String[] token = aggregation.split("-");
            int podIndex = Integer.parseInt(token[1]);
            int index = Integer.parseInt(token[2]);
            String ip = String.format("127.2.%d.%d", podIndex, index+1);
            ipTable.put(aggregation, ip);
        }
        for(String core: coreToAggregation.keySet()){
            int portNum = 10000;
            for(PositionTuple pt: coreToAggregation.get(core)){
                socketPort.put(pt, portNum++);
            }
            String[] token = core.split("-");
            int index = Integer.parseInt(token[1]);
            String ip = String.format("127.0.%d.%d", index/254 , (index%254) +1);
            ipTable.put(core, ip);
        }
    }
    void genCoreConfig(String path){
        for(String core:coreToAggregation.keySet()){
            File f = new File(path+core);
            try {
                FileWriter fw = new FileWriter(f);
                fw.write(String.format("%s %s core\n", core, ipTable.get(core)));
                for(PositionTuple corePT:coreToAggregation.get(core)){
                    PositionTuple aggregationPT = topology.get(corePT);
                    fw.write(String.format("link aggregation %s %s %s %s\n", corePT.getPortName(), ipTable.get(aggregationPT.getDeviceName()),
                            socketPort.get(corePT), socketPort.get(aggregationPT)));
                }
                fw.flush();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    void genAggregationConfig(String path){
        for(String aggregation:aggregationToEdge.keySet()){
            File f = new File(path+aggregation);
            try {
                FileWriter fw = new FileWriter(f);
                fw.write(String.format("%s %s aggregation\n", aggregation, ipTable.get(aggregation)));
                for(PositionTuple aggregationPT:aggregationToEdge.get(aggregation)){
                    PositionTuple edgePT = topology.get(aggregationPT);
                    fw.write(String.format("link edge %s %s %s %s\n", aggregationPT.getPortName(), ipTable.get(edgePT.getDeviceName()),
                            socketPort.get(aggregationPT), socketPort.get(edgePT)));
                }
                for(PositionTuple aggregationPT:aggregationToCore.get(aggregation)){
                    PositionTuple corePT = topology.get(aggregationPT);
                    fw.write(String.format("link core %s %s %s %s\n", aggregationPT.getPortName(), ipTable.get(corePT.getDeviceName()),
                            socketPort.get(aggregationPT), socketPort.get(corePT)));
                }
                fw.flush();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    void genEdgeConfig(String path){
        for(String edge:edgeToHost.keySet()){
            File f = new File(path+edge);
            try {
                FileWriter fw = new FileWriter(f);
                fw.write(String.format("%s %s edge\n", edge, ipTable.get(edge)));
                PositionTuple edgePT = edgeToHost.get(edge);
                PositionTuple hostPT = topology.get(edgePT);
                String host = hostPT.getDeviceName();
                fw.write(String.format("link h %s %s %s %s\n", edgePT.getPortName(), ipTable.get(host),
                        socketPort.get(edgePT), socketPort.get(hostPT)));
                for(PositionTuple edgePT2:edgeToAggregation.get(edge)){
                    PositionTuple aggregationPT = topology.get(edgePT2);
                    fw.write(String.format("link aggregation %s %s %s %s\n", edgePT2.getPortName(), ipTable.get(aggregationPT.getDeviceName()),
                            socketPort.get(edgePT2), socketPort.get(aggregationPT)));
                }
                fw.flush();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    void genVirtualHost(String path){
        for(String host:hostToEdge.keySet()){
            File f = new File(path+host);
            try {
                FileWriter fw = new FileWriter(f);
                fw.write(String.format("%s %s h\n", host, ipTable.get(host)));
                PositionTuple hostPT = hostToEdge.get(host);
                PositionTuple edgePT = topology.get(hostPT);
                String edge = edgePT.getDeviceName();
                fw.write(String.format("link edge %s %s %s %s\n", hostPT.getPortName(), ipTable.get(edge),
                        socketPort.get(hostPT), socketPort.get(edgePT)));
                fw.write(String.format("ip %s %s\n", hostToIpMatch.get(host).ip, hostToIpMatch.get(host).prefix));
                fw.flush();
                fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    void addTopology(String d1, String p1, String d2, String p2){
        PositionTuple pt1 = new PositionTuple(d1, p1);
        PositionTuple pt2 = new PositionTuple(d2, p2);
        setSwitchTo(pt1, pt2);
        topology.put(pt1, pt2);
        topology.put(pt2, pt1);
        setPod(d1);
        setPod(d2);
        setVirtualHost(d1);
        setVirtualHost(d2);
    }

    private void setVirtualHost(String device){
        if(!device.startsWith(edgePrefix)) return;
        String hostName = "h" + device.substring(edgePrefix.length());
        String portName1 = hostName + ">" + device;
        String portName2 = device + ">" + hostName;
        PositionTuple pt1 = new PositionTuple(hostName, portName1);
        PositionTuple pt2 = new PositionTuple(device, portName2);
        hostToEdge.put(hostName, pt1);
        edgeToHost.put(device, pt2);
        topology.put(pt1, pt2);
        topology.put(pt2, pt1);
    }

    private void readRuleAndSetHostIp(){
        for(Integer podIndex:podToAggregation.keySet()){
            Set<String> aggregations = podToAggregation.get(podIndex);
            String aggregation = aggregations.iterator().next(); // fetch one
            try{
                File file = new File(path + "rule/" + aggregation);
                InputStreamReader isr = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                String info;
                while ((info = br.readLine()) != null ){
                    if(info.length() == 0) continue;
                    String[] token = info.split("\\s+");
                    PositionTuple pt = new PositionTuple(aggregation, token[3]);
                    String edge = topology.get(pt).getDeviceName();
                    if(!edge.startsWith(edgePrefix)) continue;
                    IpMatch im = new IpMatch(Long.parseLong(token[1]), Integer.parseInt(token[2]));
                    hostToIpMatch.put(topology.get(edgeToHost.get(edge)).getDeviceName(), im);
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }

    private void setPod(String name){
        if(name.startsWith(corePrefix)) return;
        int podIndex = Integer.parseInt(name.split("-")[1]); // TODO
        if(name.startsWith(aggregationPrefix)) {
            if(!podToAggregation.containsKey(podIndex))
                podToAggregation.put(podIndex, new HashSet<>());
            podToAggregation.get(podIndex).add(name);
        }else if(name.startsWith(edgePrefix)){
            if(!podToEdge.containsKey(podIndex))
                podToEdge.put(podIndex, new HashSet<>());
            podToEdge.get(podIndex).add(name);
        }
        switchToPod.put(name, podIndex);
    }

    private void setSwitchTo(PositionTuple pt1, PositionTuple pt2){
        String device1 = pt1.getDeviceName(), device2 = pt2.getDeviceName();
        // device1 -> device2 is core -> aggregation or aggregation -> edge
        if(device2.startsWith(corePrefix) || device1.startsWith(edgePrefix)){
            String t = device1; device1 = device2; device2 = t;
            PositionTuple ptt = pt1; pt1 = pt2; pt2 = ptt;
        }
        Map<String, Set<PositionTuple>> map1 = null;
        Map<String, Set<PositionTuple>> map2 = null;

        if(device1.startsWith(corePrefix)){
            map1 = coreToAggregation;
            map2 = aggregationToCore;
        }else{
            map1 = aggregationToEdge;
            map2 = edgeToAggregation;
        }
        if(!map1.containsKey(device1)) map1.put(device1, new HashSet<>());
        if(!map2.containsKey(device2)) map2.put(device2, new HashSet<>());
        map1.get(device1).add(pt1);
        map2.get(device2).add(pt2);
    }

    void readTopologyFile(String filename){
        try{
            File file = new File(filename);
            InputStreamReader isr = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8);
            BufferedReader br = new BufferedReader(isr);
            String info;
            while ((info = br.readLine()) != null ){
                if(info.length() == 0) continue;
                String[] token = info.split("\\s+");
                addTopology(token[0], token[1], token[2], token[3]);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public FatTreeController(){
        coreToAggregation = new HashMap<>();
        aggregationToEdge = new HashMap<>();
        aggregationToCore = new HashMap<>();
        edgeToAggregation = new HashMap<>();
        podToAggregation = new HashMap<>();
        podToEdge = new HashMap<>();
        topology = new HashMap<>();
        switchToPod = new HashMap<>();
        hostToIpMatch = new HashMap<>();
        ipTable = new HashMap<>();
        edgeToHost = new HashMap<>();
        hostToEdge = new HashMap<>();
        socketPort = new HashMap<>();
    }

    static class IpMatch{
        long ip;
        int prefix;
        IpMatch(long ip, int prefix){
            this.ip = ip;
            this.prefix = prefix;
        }

        @Override
        public String toString() {
            return ip + " " + prefix;
        }
    }
}


